#set( $symbol_pound = '#' )
#set( $symbol_dollar = '$' )
#set( $symbol_escape = '\' )
package ${package}.common;

import ${package}.common.annotation.Only;
import ${package}.common.exception.DataDuplicationException;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.NonNull;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 基础服务
 *
 * @author 吴涵华
 * @since 2024/06/06 16:16:27
 */
@Slf4j
public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> extends ServiceImpl<M, T> {
    /**
     * 得到只有键地图
     *
     * @param tableInfo 表信息
     * @return {@link Map }<{@link String }唯一键, {@link Map }<{@link String }列名, {@link Object }值>>
     */
    private Map<String, Map<String, Object>> getOnlyKeyMap(TableInfo tableInfo, T data) {
        List<TableFieldInfo> fieldInfoList = tableInfo.getFieldList();
        Map<String, Map<String, Object>> fieldMap = new HashMap<>();
        for (TableFieldInfo fieldInfo : fieldInfoList) {
            Field field = fieldInfo.getField();
            Only only = field.getAnnotation(Only.class);
            if (only == null) {
                continue;
            }
            String key = only.value();
            if (!fieldMap.containsKey(key)) {
                fieldMap.put(key, new HashMap<>());
            }
            fieldMap.get(key).put(fieldInfo.getColumn(), getFieldVal(field, data));
        }
        return fieldMap;
    }

    private Object getFieldVal(Field field, T data) {
        field.setAccessible(true);
        try {
            return field.get(data);
        } catch (IllegalAccessException e) {
            log.error("获取主键值报错:{}", e.getMessage());
            return null;
        }
    }

    private Object getIdVal(TableInfo tableInfo, T data) {
        if (!tableInfo.havePK()) {
            return null;
        }
        //主键属性名
        String p = tableInfo.getKeyProperty();
        for (Field field : data.getClass().getDeclaredFields()) {
            if (!field.getName().equals(p)) {
                continue;
            }
            return getFieldVal(field, data);
        }
        return null;
    }

    /*
     * 检查数据唯一性
     *
     * @return boolean true:唯一 false:不唯一
     */
    public boolean checkOnly(T data, boolean isThrow) {
        TableInfo tableInfo = TableInfoHelper.getTableInfo(data.getClass());
        //收集每个唯一键对应的字段列表
        Map<String, Map<String, Object>> fieldMap = getOnlyKeyMap(tableInfo, data);
        if (fieldMap.isEmpty()) {
            return true;
        }
        Object id = getIdVal(tableInfo, data);

        for (Map.Entry<String, Map<String, Object>> entry : fieldMap.entrySet()) {
            QueryChainWrapper<T> queryChainWrapper = query();
            if (id != null) {
                queryChainWrapper.ne(tableInfo.getKeyColumn(), id);//排除自己
            }
            entry.getValue().forEach(queryChainWrapper::eq);
            //执行查询
            Long count = queryChainWrapper.count();
            if (count > 0) {//存在重复的
                if (!isThrow)//不用抛出异常
                    return false;
                throw new DataDuplicationException(entry.getKey());
            }
        }
        return true;
    }

    /*
     * 检查数据唯一性
     *
     * @return boolean true:唯一 false:不唯一
     */
    public boolean checkOnly(T data) {
        return checkOnly(data, false);
    }

    /*
     * 检查数据唯一性 检查失败抛出异常
     *
     * @return boolean true:唯一 false:不唯一
     */
    public void checkOnlyThrow(T data) throws DataDuplicationException {
        checkOnly(data, true);
    }

    @SneakyThrows
    public void checkOnlyThrow(T data, Throwable throwable) {
        if (checkOnly(data)) {
            return;
        }
        throw throwable;
    }

    @SuppressWarnings("unchecked")
    public <TT> T convert(@NonNull TT source) {
        if (source.getClass().equals(getEntityClass())) {
            return (T) source;
        }
        return BeanUtil.copyProperties(source, getEntityClass());
    }
}
